function graphicsEngine(map, gameObjectFactory) {
	this.canvas = $('#main')[0];
	this.context = this.canvas.getContext('2d');
	this.miniMap = $('#miniMap')[0];
	this.miniMapContext = this.miniMap.getContext('2d');

    this.images = new imageLoader(gameObjectTypes);
	this.background = this.images['desertMap'];
	this.map = map;
    
    this.canvas.selectRectX = 0;
	this.canvas.selectRectY = 0;
	this.canvas.selectRectEndX = 0;
	this.canvas.selectRectEndY = 0;

	this.cameraX = 0;
	this.cameraY = 0;

	this.debugMap = false;
	this.selectColor = '#fff';

	this.structureToBuild = null;
	this.structures = $('#structures');
	this.units = $('#units');

	this.unitTitle = $('#unitTitle');
	this.unitStatus = $('#unitStatus');
}

graphicsEngine.prototype.putCamera = function(x, y) {
	var xMax = this.map.width * this.map.tileSize - this.canvas.width;
	var yMax = this.map.height * this.map.tileSize - this.canvas.height;

	this.cameraX = x >= 0 ? x < xMax ? x : xMax : 0;
	this.cameraY = y >= 0 ? y < yMax ? y : yMax : 0;
}

graphicsEngine.prototype.moveCamera = function(x, y) {
	var newX = this.cameraX + x;
	var newY = this.cameraY + y;
	var xMax = this.map.width * this.map.tileSize - this.canvas.width;
	var yMax = this.map.height * this.map.tileSize - this.canvas.height;
	
	this.cameraX = newX >= 0 ? newX < xMax ? newX : xMax : 0;
	this.cameraY = newY >= 0 ? newY < yMax ? newY : yMax : 0;
}

graphicsEngine.prototype.setBuildButtons = function(gameObjectFactory, player) {
	for(var goName in gameObjectTypes){
		var template = gameObjectFactory[goName+'Template'];
		if(template.faction === player.faction) {
			if(template.moveSpeed === 0) {
				this.structures.html(this.structures.html() + 	'<button id="'+goName+'" type="button">' +
																	'<img src="content/sprites/'+goName+'Idle.png" width="55" height="55"/>' +
																	'<div>'+template.price+'</div>' +
																'</button>');
			}
			else {
				this.units.html(this.units.html() + 	'<button id="'+goName+'" type="button">' +
															'<img src="content/sprites/'+goName+'Idle.png" width="55" height="55"/>' +
															'<div>'+template.price+'</div>' +
														'</button>');
			}
		}
	}

	this.structures.children().filter(':button').click(function(event) {
		game.graphicsEngine.structureToBuild = {
			templateName: this.id,
			x: 0,
			y: 0
		};
	});

	this.units.children().filter(':button').click(function(event) {
		var type = gameObjectTypes[this.id];
		var buildFrom = game.buildFrom(type);
        if(buildFrom !== null) {
			game.networkingEngine.queueBuildUnit(buildFrom.id, type);
		}
	});
}

graphicsEngine.prototype.draw = function(gameObjects, selected) {
	this.context.fillStyle = '#000';
	this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);

	this.context.drawImage(this.background, -this.cameraX, -this.cameraY);
	this.miniMapContext.drawImage(this.background, 0, 0, this.miniMap.width, this.miniMap.height);

	//debug
	if(this.debugMap) {
		for(var i = 0; i < this.map.width; i++) {
			for(var j = 0; j < this.map.height; j++) {
				if(!this.map.tiles[i][j].passable) {
					this.context.fillStyle = '#f00';
					this.context.fillRect(i * this.map.tileSize - this.cameraX, j * this.map.tileSize - this.cameraY, this.map.tileSize, this.map.tileSize);
				}
				else if(this.map.tiles[i][j].occupiedBy !== null) {
					this.context.fillStyle = '#800';
					this.context.fillRect(i * this.map.tileSize - this.cameraX, j * this.map.tileSize - this.cameraY, this.map.tileSize, this.map.tileSize);
				}
			}
		}
	}

	for(var goId in gameObjects){
		var go = gameObjects[goId];

		// main canvas
		this.context.drawImage(go.attackTimer > go.attackSpeed - .25 ? go.attackImage : go.idleImage, go.x - this.cameraX, go.y - this.cameraY, go.width, go.height);

		// mini map
		this.miniMapContext.drawImage(this.images['dot'+go.player.id], go.x / this.map.pixelWidth * this.miniMap.width,
		go.y / this.map.pixelHeight * this.miniMap.height,
		go.width / this.map.pixelWidth * this.miniMap.width,
		go.height / this.map.pixelHeight * this.miniMap.height);
	}

	for(var goId in gameObjects){
		var go = gameObjects[goId];

    	var normalizedHealth = go.health / go.maxHealth;
    	var fillStyle = 'rgb('+Math.floor((1 - normalizedHealth)*255)+','+Math.floor(normalizedHealth*255)+',0)';
		this.context.fillStyle = fillStyle;
		this.context.fillRect(go.x - this.cameraX, go.y - this.cameraY - 4, go.width * normalizedHealth, 2);

		if(go.player === game.player && go.buildQueue.length > 0) {
			var queue = go.buildQueue;
			var normalizeBuildTime = (queue[0].buildTime-go.buildTimer)/queue[0].buildTime;
			this.context.fillStyle = '#08f';
			this.context.fillRect(go.x - this.cameraX, go.y - this.cameraY + 2, go.width * normalizeBuildTime, 2);
		}
	}

	// ghost outline for structures the player is trying to place
	if(this.structureToBuild) {
		var template = game.gameObjectFactory[this.structureToBuild.templateName+'Template'];
		var img = this.images[this.structureToBuild.templateName+'Idle'+game.player.id];
		var tileX = Math.floor((this.cameraX + this.structureToBuild.x) / this.map.tileSize);
		var tileY = Math.floor((this.cameraY + this.structureToBuild.y) / this.map.tileSize);
		if(this.map.tileInBounds(tileX, tileY) && this.map.tileInBounds(tileX + template.width / this.map.tileSize - 1, tileY + template.height / this.map.tileSize - 1)) {
			this.context.drawImage(img, tileX * this.map.tileSize - this.cameraX, tileY * this.map.tileSize - this.cameraY, template.width, template.height);
			var canBuild = game.canBuildStructure(template, game.player, tileX, tileY);
			for(var i = tileX; i < tileX + template.width / this.map.tileSize; i++) {
				for(var j = tileY; j < tileY + template.height / this.map.tileSize; j++) {
					if(!canBuild || !this.map.tiles[i][j].passable) {
						this.context.fillStyle = 'rgba(255,0,0,.5)';
						this.context.fillRect(i * this.map.tileSize - this.cameraX, j * this.map.tileSize - this.cameraY, this.map.tileSize, this.map.tileSize);
					}
					else {
						this.context.fillStyle = 'rgba(0,255,0,.5)';
						this.context.fillRect(i * this.map.tileSize - this.cameraX, j * this.map.tileSize - this.cameraY, this.map.tileSize, this.map.tileSize);
					}
				}
			}
		}
	}

	$('#cancelButtons').hide();
	this.unitTitle.html('');
	this.unitStatus.html('');
	if(game.selected.length > 0) {
		var selectedUnit = game.selected[0];
		this.unitTitle.html('<img src="'+selectedUnit.idleImage.src+'" width="32" height="32" style="float: left;"/><div style="float: left; margin: 8px 0 0 8px; color: #0f0;">'+selectedUnit.friendlyName+'</div>');
		if(selectedUnit.buildQueue.length > 0 && selectedUnit.player.id === game.player.id) {
			$('#cancelButtons').show();
			var queue = selectedUnit.buildQueue;
			if(queue.length > 0) {
				var buildProgress = (queue[0].buildTime-selectedUnit.buildTimer)/queue[0].buildTime*this.unitStatus.width();
				this.unitStatus.html('<div style="display: block; width: '+buildProgress+'px; height: 4px; background-color: #08f;"></div>');
				for(var i = 0; i < queue.length; i++){
					var template = queue[i];
					var unitName = game.gameObjectFactory.findGameObjectNameByTemplate(template);
					this.unitStatus.html(this.unitStatus.html() + 	'<img src="content/sprites/' + 
																	unitName+'Idle'+game.player.id + '.png"' +
																	'width="32" height="32"/>');
				}
			}
		}
		else {
			this.unitStatus.html(	'<div style="color: #0f0;">Health: '+selectedUnit.health+'/'+selectedUnit.maxHealth+'</div>' +
									'<div style="color: #0f0;">Damage: '+selectedUnit.damage+'</div>');
		}

		for(var i = 0; i < selected.length; i++) {
			var go = selected[i];
			this.context.strokeStyle = this.selectColor;
			this.context.lineWidth = 2;
			this.context.strokeRect(go.x - this.cameraX, go.y - this.cameraY, go.width, go.height);
		}
	}
	else {
		this.unitStatus.html('<div style="color: red; font-weight: bold; text-align: center;">Lizard Warz</div>');
	}

	this.miniMapContext.strokeStyle = '#fff';
	this.miniMapContext.lineWidth = 2;
	this.miniMapContext.strokeRect(this.cameraX / this.map.pixelWidth * this.miniMap.width,
	this.cameraY / this.map.pixelHeight * this.miniMap.height,
	this.canvas.width / this.map.pixelWidth * this.miniMap.width,
	this.canvas.height / this.map.pixelHeight * this.miniMap.height);

	this.context.strokeStyle = this.selectColor;
	this.context.strokeRect(Math.min(this.canvas.selectRectX, this.canvas.selectRectEndX),
		Math.min(this.canvas.selectRectY, this.canvas.selectRectEndY),
		Math.abs(this.canvas.selectRectX - this.canvas.selectRectEndX),
		Math.abs(this.canvas.selectRectY - this.canvas.selectRectEndY));
}
